import { Mesh } from './Mesh.js';
import { Matrix4 } from '../math/Matrix4.js';
import { Vector3 } from '../math/Vector3.js';
import { Vector4 } from '../math/Vector4.js';

const _basePosition = /* @__PURE__ */ new Vector3();

const _skinIndex = /* @__PURE__ */ new Vector4();
const _skinWeight = /* @__PURE__ */ new Vector4();

const _vector = /* @__PURE__ */ new Vector3();
const _matrix = /* @__PURE__ */ new Matrix4();

class SkinnedMesh extends Mesh {
  constructor(geometry, material) {
    super(geometry, material);

    this.type = 'SkinnedMesh';

    this.bindMode = 'attached';
    this.bindMatrix = new Matrix4();
    this.bindMatrixInverse = new Matrix4();
  }

  copy(source) {
    super.copy(source);

    this.bindMode = source.bindMode;
    this.bindMatrix.copy(source.bindMatrix);
    this.bindMatrixInverse.copy(source.bindMatrixInverse);

    this.skeleton = source.skeleton;

    return this;
  }

  bind(skeleton, bindMatrix) {
    this.skeleton = skeleton;

    if (bindMatrix === undefined) {
      this.updateMatrixWorld(true);

      this.skeleton.calculateInverses();

      bindMatrix = this.matrixWorld;
    }

    this.bindMatrix.copy(bindMatrix);
    this.bindMatrixInverse.copy(bindMatrix).invert();
  }

  pose() {
    this.skeleton.pose();
  }

  normalizeSkinWeights() {
    const vector = new Vector4();

    const skinWeight = this.geometry.attributes.skinWeight;

    for (let i = 0, l = skinWeight.count; i < l; i++) {
      vector.x = skinWeight.getX(i);
      vector.y = skinWeight.getY(i);
      vector.z = skinWeight.getZ(i);
      vector.w = skinWeight.getW(i);

      const scale = 1.0 / vector.manhattanLength();

      if (scale !== Infinity) {
        vector.multiplyScalar(scale);
      } else {
        vector.set(1, 0, 0, 0); // do something reasonable
      }

      skinWeight.setXYZW(i, vector.x, vector.y, vector.z, vector.w);
    }
  }

  updateMatrixWorld(force) {
    super.updateMatrixWorld(force);

    if (this.bindMode === 'attached') {
      this.bindMatrixInverse.copy(this.matrixWorld).invert();
    } else if (this.bindMode === 'detached') {
      this.bindMatrixInverse.copy(this.bindMatrix).invert();
    } else {
      console.warn(`THREE.SkinnedMesh: Unrecognized bindMode: ${this.bindMode}`);
    }
  }

  boneTransform(index, target) {
    const skeleton = this.skeleton;
    const geometry = this.geometry;

    _skinIndex.fromBufferAttribute(geometry.attributes.skinIndex, index);
    _skinWeight.fromBufferAttribute(geometry.attributes.skinWeight, index);

    _basePosition.fromBufferAttribute(geometry.attributes.position, index).applyMatrix4(this.bindMatrix);

    target.set(0, 0, 0);

    for (let i = 0; i < 4; i++) {
      const weight = _skinWeight.getComponent(i);

      if (weight !== 0) {
        const boneIndex = _skinIndex.getComponent(i);

        _matrix.multiplyMatrices(skeleton.bones[boneIndex].matrixWorld, skeleton.boneInverses[boneIndex]);

        target.addScaledVector(_vector.copy(_basePosition).applyMatrix4(_matrix), weight);
      }
    }

    return target.applyMatrix4(this.bindMatrixInverse);
  }
}

SkinnedMesh.prototype.isSkinnedMesh = true;

export { SkinnedMesh };
